﻿#include "Window.h"

#include "Core/Event.h"
#include "Render/ColorRenderPass.h"
#include "Asset/AssetManager.h"

// lib
#include <GLFW/glfw3.h>
#include <iostream>

// std
#include <functional>

static Window *s_instance = nullptr;

Window::Window(int width, int height, const std::string &title)
    : m_width(width), m_height(height), m_title(title)
{
  assert(!s_instance && "Window already exists!");
  s_instance = this;
  InitWindow();
}

Window::~Window()
{
  if (m_window)
  {
    glfwDestroyWindow(m_window);
    m_window = nullptr;
    AssetManager::GetInstance().UnloadAll();
    glfwTerminate();
  }

  if (s_instance == this)
    s_instance = nullptr;
}

void Window::InitWindow()
{
  glfwInit();
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

  m_window = glfwCreateWindow(m_width, m_height, m_title.c_str(), nullptr, nullptr);
  if (!m_window)
  {
    LOG_ERROR("Failed to create GLFW window");
    glfwTerminate();
    return;
  }
  glfwMakeContextCurrent(m_window);
  glfwSetWindowUserPointer(m_window, this);
  glfwSwapInterval(0);
  SetCallbacks();

  m_aspectRatio = (float)m_width / (float)m_height;

  if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
  {
    LOG_ERROR("Failed to initialize GLAD");
  }

  m_frameBufferStack->SetKeepIntermediateTextures(true);

  SetClearColor(glm::vec4{0.f});

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  
}

void Window::SetCallbacks() const
{
  glfwSetFramebufferSizeCallback(m_window, FramebufferResizeCallback);
  glfwSetMouseButtonCallback(m_window, MouseButtonCallback);
  glfwSetCursorPosCallback(m_window, CursorPosCallback);
  glfwSetScrollCallback(m_window, ScrollCallback);
  glfwSetKeyCallback(m_window, KeyCallback);

  glfwSetErrorCallback(Window::OnWindowError);
}

void Window::BeginFrame()
{
  assert(m_frameBufferStack->IsEmpty());
  ResetFrame();

  if (!m_frameBuffer || m_frameBuffer->GetWidth() != m_width || m_frameBuffer->GetHeight() != m_height)
    m_frameBuffer = CreateRef<FrameBuffer>(m_width, m_height, true, 1); // 1
  m_frameBufferStack->Push(m_frameBuffer); // 0
  ResetFrame();
}

void Window::ResetFrame()
{
  glViewport(0, 0, m_width, m_height);
  glClearColor(m_clearColor.r * m_clearColor.a, m_clearColor.g * m_clearColor.a, m_clearColor.b * m_clearColor.a, m_clearColor.a);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void Window::FinalizeFrame()
{
  assert(m_frameBufferStack->GetSize() == 1);

  ColorRenderPass::RenderTexture(m_frameBufferStack->Pop()->GetColorAttachment(0));
}

bool Window::ShouldClose() const
{
  return glfwWindowShouldClose(m_window);
}

void Window::SwapBuffers()
{
  m_frameBufferStack->ClearIntermediateTexturesReferences();
  glfwSwapBuffers(m_window);
}

void Window::PollEvents()
{
  glfwPollEvents();
}

bool Window::ShouldRender() const
{
  return m_width > 0 && m_height > 0;
}

float Window::GetAspectRatio() const
{
  return m_aspectRatio;
}

void Window::SetEnableCursor(bool enable) const
{
  if (enable)
  {
    // 显示光标
    glfwSetInputMode(m_window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
    // 重置鼠标位置到窗口中心
    glfwSetCursorPos(m_window, m_width / 2.0, m_height / 2.0);
  }
  else
  {
    glfwSetInputMode(m_window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  }
}

// callbacks
void Window::FramebufferResizeCallback(GLFWwindow *glWindow, int width, int height)
{
  auto window = reinterpret_cast<Window *>(glfwGetWindowUserPointer(glWindow));
  glViewport(0, 0, width, height);
  window->m_aspectRatio = (float)width / (float)height;

  Window *win = static_cast<Window *>(glfwGetWindowUserPointer(glWindow));
  win->m_width = width;
  win->m_height = height;
  // 发送自定义事件
  WindowResizeEvent event(width, height);
  win->m_dispatcher.Dispatch(event);
}

void Window::MouseButtonCallback(GLFWwindow *window, int button, int action, int mods)
{
  Window *win = static_cast<Window *>(glfwGetWindowUserPointer(window));
  if (action == GLFW_PRESS)
  {
    MouseButtonPressedEvent event(button);
    win->m_dispatcher.Dispatch(event);
  }
  else if (action == GLFW_RELEASE)
  {
    MouseButtonReleasedEvent event(button);
    win->m_dispatcher.Dispatch(event);
  }
}

void Window::CursorPosCallback(GLFWwindow *window, double xPos, double yPos)
{
  Window *win = static_cast<Window *>(glfwGetWindowUserPointer(window));
  MouseMovedEvent event(static_cast<float>(xPos), static_cast<float>(yPos));
  win->m_dispatcher.Dispatch(event);
}

void Window::ScrollCallback(GLFWwindow *window, double xOffset, double yOffset)
{
  Window *win = static_cast<Window *>(glfwGetWindowUserPointer(window));
  MouseScrolledEvent event(static_cast<float>(xOffset), static_cast<float>(yOffset));
  win->m_dispatcher.Dispatch(event);
}

void Window::KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
  Window *win = static_cast<Window *>(glfwGetWindowUserPointer(window));
  if (action == GLFW_PRESS)
  {
    KeyPressedEvent event(key, 0); // repeatCount暂设为0
    win->m_dispatcher.Dispatch(event);
  }
  else if (action == GLFW_RELEASE)
  {
    KeyReleasedEvent event(key);
    win->m_dispatcher.Dispatch(event);
  }
  else if (action == GLFW_REPEAT)
  {
    KeyPressedEvent event(key, 1);
    win->m_dispatcher.Dispatch(event);
  }
}

void Window::OnWindowError(int32_t errorCode, const char *description)
{
  LOG_ERROR("GLFW Error ({0}): {1}", errorCode, description);
}

void GLAPIENTRY Window::OnOpenGlMessage(GLenum source,
                                        GLenum type,
                                        GLuint id,
                                        GLenum severity,
                                        GLsizei,
                                        const GLchar *message,
                                        const void *)
{
  if (id == 131185 || id == 131218 || id == 131169 || id == 131076 || id == 131204)
  {
    return;
  }

  std::stringstream ss;

  ss << "---------------" << std::endl;
  ss << "Debug message (" << id << "): " << message << std::endl;

  switch (source)
  {
  case GL_DEBUG_SOURCE_API:
    ss << "Source: API";
    break;
  case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
    ss << "Source: Window System";
    break;
  case GL_DEBUG_SOURCE_SHADER_COMPILER:
    ss << "Source: Shader Compiler";
    break;
  case GL_DEBUG_SOURCE_THIRD_PARTY:
    ss << "Source: Third Party";
    break;
  case GL_DEBUG_SOURCE_APPLICATION:
    ss << "Source: Application";
    break;
  case GL_DEBUG_SOURCE_OTHER:
    ss << "Source: Other";
    break;
  default:
    break;
  }
  ss << std::endl;

  switch (type)
  {
  case GL_DEBUG_TYPE_ERROR:
    ss << "Type: Error";
    break;
  case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
    ss << "Type: Deprecated Behaviour";
    break;
  case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
    ss << "Type: Undefined Behaviour";
    break;
  case GL_DEBUG_TYPE_PORTABILITY:
    ss << "Type: Portability";
    break;
  case GL_DEBUG_TYPE_PERFORMANCE:
    ss << "Type: Performance";
    break;
  case GL_DEBUG_TYPE_MARKER:
    ss << "Type: Marker";
    break;
  case GL_DEBUG_TYPE_PUSH_GROUP:
    ss << "Type: Push Group";
    break;
  case GL_DEBUG_TYPE_POP_GROUP:
    ss << "Type: Pop Group";
    break;
  case GL_DEBUG_TYPE_OTHER:
    ss << "Type: Other";
    break;
  default:
    break;
  }
  ss << std::endl;

  switch (severity)
  {
  case GL_DEBUG_SEVERITY_HIGH:
    ss << "Severity: high";
    break;
  case GL_DEBUG_SEVERITY_MEDIUM:
    ss << "Severity: medium";
    break;
  case GL_DEBUG_SEVERITY_LOW:
    ss << "Severity: low";
    break;
  case GL_DEBUG_SEVERITY_NOTIFICATION:
    ss << "Severity: notification";
    break;
  default:
    break;
  }
  ss << std::endl;
  ss << std::endl;

  LOG_TRACE("{0}", ss.str());
}

Window &Window::GetInstance()
{
  assert(s_instance && "Window not initialized!");
  return *s_instance;
}